package com.askey.bitratedemo;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Range;
import android.util.Size;
import android.view.Surface;
import android.widget.Toast;

import androidx.annotation.NonNull;

import java.util.Arrays;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class CAMERA {
    private String TAG = "CAMERA-";
    private final CameraManager manager;
    private final Context mContext;
    private HandlerThread mBackgroundThread;
    private Handler mBackgroundHandler;
    private Surface mSurface;
    private String mCameraIDStr = "";
    private static Semaphore mCameraLock = new Semaphore(1);

    public CAMERA(Integer id, Surface surface, Context context) {
        mContext = context;
        mSurface = surface;
        manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
        mCameraIDStr = getCameraStrForId(id);
        TAG += " " + mCameraIDStr;
    }

    private String getCameraStrForId(int id) {
        String idString = "";
        try {
            for (String cameraId : manager.getCameraIdList()) {

                CameraCharacteristics characteristics
                        = manager.getCameraCharacteristics(cameraId);

                Integer cam = characteristics.get(CameraCharacteristics.LENS_FACING);
                if (cam != null && cam == id) {
                    idString = cameraId;
                    break;
                }
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return idString;
    }

    public static Range<Integer>[] getFpsRange(Context context, Integer id) {
        CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
        try {
            for (String cameraId : manager.getCameraIdList()) {

                CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
                Integer cam = characteristics.get(CameraCharacteristics.LENS_FACING);
                if (cam != null && cam.equals(id)) {
                    StreamConfigurationMap streamConfigurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); //
                    Size[] outputSize = streamConfigurationMap.getOutputSizes(SurfaceTexture.class);
                    Log.d("CAMERA", "Sizes:" + Arrays.toString(outputSize));
                    Range<Integer>[] fpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
                    Log.d("CAMERA", "SYNC_MAX_LATENCY_PER_FRAME_CONTROL: " + Arrays.toString(fpsRanges));
                    return fpsRanges;
                }
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    private CameraDevice mCameraDevice;
    private CameraCaptureSession.CaptureCallback ccb = new CameraCaptureSession.CaptureCallback() {
        @Override
        public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
            super.onCaptureStarted(session, request, timestamp, frameNumber);
//            Log.d(TAG, "onCaptureStarted");
        }

        @Override
        public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
            super.onCaptureProgressed(session, request, partialResult);
//            Log.d(TAG, "onCaptureProgressed");
        }

        @Override
        public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
            super.onCaptureCompleted(session, request, result);
//            Log.d(TAG, "onCaptureCompleted");
        }

        @Override
        public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
            super.onCaptureFailed(session, request, failure);
//            Log.d(TAG, "onCaptureFailed");
        }

        @Override
        public void onCaptureSequenceCompleted(@NonNull CameraCaptureSession session, int sequenceId, long frameNumber) {
            super.onCaptureSequenceCompleted(session, sequenceId, frameNumber);
//            Log.d(TAG, "onCaptureSequenceCompleted");
        }

        @Override
        public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session, int sequenceId) {
            super.onCaptureSequenceAborted(session, sequenceId);
//            Log.d(TAG, "onCaptureSequenceAborted");
        }

        @Override
        public void onCaptureBufferLost(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull Surface target, long frameNumber) {
            super.onCaptureBufferLost(session, request, target, frameNumber);
//            Log.d(TAG, "onCaptureBufferLost");
        }
    };

    private CameraCaptureSession mSession;
    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            Log.d(TAG, "onOpened");
            mCameraLock.release();
            mCameraDevice = cameraDevice;
            try {
                final CaptureRequest.Builder mPreviewRequestBuilder =
                        mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                mPreviewRequestBuilder.addTarget(mSurface);

                mCameraDevice.createCaptureSession(Arrays.asList(mSurface),
                        new CameraCaptureSession.StateCallback() {
                            @Override
                            public void onConfigured(@NonNull CameraCaptureSession session) {
                                try {
                                    mSession = session;

                                    mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                            CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);

                                    mSession.setRepeatingRequest(mPreviewRequestBuilder.build(), ccb, mBackgroundHandler);
                                    Log.d(TAG, "Start repeating");
                                } catch (CameraAccessException e) {
                                    e.printStackTrace();
                                }
                            }

                            @Override
                            public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                                Log.e(TAG, "onConfigureFailed");

                                mSession = null;
                                if (mCameraDevice != null)
                                    mCameraDevice.close();
                                mCameraDevice = null;
                            }
                        }, mBackgroundHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            Log.e(TAG, "onDisconnected");
            mCameraLock.release();
            cameraDevice.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int error) {
            mCameraLock.release();
            cameraDevice.close();
            mCameraDevice = null;
            mSession = null;
            mSurface.release();
            Log.e(TAG, "Error = " + error);
            Toast.makeText(mContext, TAG + " Error=" + error, Toast.LENGTH_LONG).show();
        }

    };


    @SuppressLint("MissingPermission")
    public void start() {
        mBackgroundThread = new HandlerThread("CameraBackground-1");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());

        if (mCameraIDStr.isEmpty()) {
            Log.e(TAG, "Camera Id invalid.");
            Toast.makeText(mContext, TAG + " Camera Id invalid.", Toast.LENGTH_LONG).show();
            return;
        }

        try {
            if (!mCameraLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                throw new RuntimeException("Time out waiting to lock camera opening.");
            }
            manager.openCamera(mCameraIDStr, mStateCallback, mBackgroundHandler);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    public void stop() {

        try {
            if (mSession != null) {
                Log.w(TAG, "Session stopRepeating in.");
                mSession.stopRepeating();
                Log.w(TAG, "Session stopRepeating out.");
            }

            if (mCameraDevice != null) {
                Log.w(TAG, "Device close in.");
                mCameraDevice.close();
                Log.w(TAG, "Device close out.");
            }

        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        mCameraLock.release();
        mBackgroundThread.quitSafely();

        try {
            mBackgroundThread.join();
            mBackgroundThread = null;
            mBackgroundHandler = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
